using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using CriticalSystems.Instrumentation.Internal;

namespace CriticalSystems.Instrumentation.Configuration
{
    /// <summary>
    /// Holds the configuration information for instrumentation.
    /// </summary>
    public sealed class InstrumentationConfiguration : ConfigurationSection, IInstrumentationConfiguration
    {
        private const string SectionName = "criticalsystems.instrumentation";

        /// <summary>
        /// Watches for configuration file changes.
        /// </summary>
        private static readonly FileSystemWatcher watcher;
        /// <summary>
        /// The singleton configuration instance in the default configuration file.
        /// </summary>
        private static InstrumentationConfiguration singleton;
        private readonly IList<Regex> subsystemNamespaces = new List<Regex>();

        /// <summary>
        /// Creates a new diagnostics configuration.
        /// </summary>
        static InstrumentationConfiguration()
        {
            string configPath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            watcher = new FileSystemWatcher(Path.GetDirectoryName(configPath), Path.GetFileName(configPath));
            watcher.Changed += OnConfigurationFileChanged;
            singleton = ConfigurationManager.GetSection(SectionName) as InstrumentationConfiguration ??
                        new InstrumentationConfiguration();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="InstrumentationConfiguration"/> class.
        /// </summary>
        public InstrumentationConfiguration()
        {
            this["tracing"] = TracingFlags.TraceEntryExit | TracingFlags.TraceInfo | TracingFlags.TraceError |
                              TracingFlags.TraceWarning;
        }

        /// <summary>
        /// Gets the configuration instance from the default configuration file.
        /// </summary>
        public static InstrumentationConfiguration Instance
        {
            get { return singleton; }
        }

        #region IInstrumentationConfiguration Members
        /// <summary>
        /// Gets or sets the trace writer settings.
        /// </summary>
        [ConfigurationProperty("traceWriter", IsRequired = false)]
        public WriterElement TraceWriter
        {
            get { return (WriterElement) this["traceWriter"]; }

            set { this["traceWriter"] = value; }
        }

        /// <summary>
        /// Gets or sets the log writer settings.
        /// </summary>
        [ConfigurationProperty("logWriter", IsRequired = false)]
        public WriterElement LogWriter
        {
            get { return (WriterElement) this["logWriter"]; }

            set { this["logWriter"] = value; }
        }

        /// <summary>
        /// Gets or sets the tracing configuration settings.
        /// </summary>
        [ConfigurationProperty("tracing", DefaultValue = TracingFlags.TraceEntryExit | TracingFlags.TraceInfo | TracingFlags.TraceError | TracingFlags.TraceWarning)]
        public TracingFlags Tracing
        {
            get { return (TracingFlags) this["tracing"]; }

            set { this["tracing"] = value; }
        }

        /// <summary>
        /// Gets or sets the tracing configuration settings.
        /// </summary>
        [ConfigurationProperty("logging", DefaultValue = LoggingFlags.LogFirstAbnormalExit | LoggingFlags.LogThreadAbnormalExit | LoggingFlags.LogSubsystemAbnormalExit)]
        public LoggingFlags Logging
        {
            get { return (LoggingFlags)this["logging"]; }

            set { this["logging"] = value; }
        }

        /// <summary>
        /// Gets or sets the system name
        /// </summary>
        [ConfigurationProperty("system")]
        public string System
        {
            get { return (string) this["system"]; }

            set { this["system"] = value; }
        }

        /// <summary>
        /// Gets or sets the subsystem name
        /// </summary>
        [ConfigurationProperty("subsystem")]
        public string Subsystem
        {
            get { return (string) this["subsystem"]; }

            set { this["subsystem"] = value; }
        }

        /// <summary>
        /// Gets or sets the subsystem name
        /// </summary>
        [ConfigurationProperty("subsystemNamespaces")]
        public string SubsystemNamespaces
        {
            get { return (string) this["subsystemNamespaces"]; }

            set
            {
                string[] strings = value.Split(new char[]
                                                   {
                                                       ',',
                                                       ';',
                                                       ' '
                                                   }, StringSplitOptions.RemoveEmptyEntries);
                foreach (string ns in strings)
                {
                    string nsRegex = ns.Trim().Replace(".", @"\.");
                    nsRegex = nsRegex.Replace("*", @"[\w\.]+");
                    this.subsystemNamespaces.Add(new Regex(nsRegex, RegexOptions.Compiled));
                }
                this["subsystemNamespaces"] = value;
            }
        }

        /// <summary>
        /// Gets or sets the description
        /// </summary>
        [ConfigurationProperty("description")]
        public string Description
        {
            get { return (string) this["description"]; }

            set { this["description"] = value; }
        }
        #endregion

        /// <summary>
        /// Handles the configuration file changing.
        /// </summary>
        /// <param name="sender">
        /// The event source.
        /// </param>
        /// <param name="e">
        /// Information about the event.
        /// </param>
        private static void OnConfigurationFileChanged(object sender, FileSystemEventArgs e)
        {
            ConfigurationManager.RefreshSection(SectionName);
            Thread.MemoryBarrier();
            singleton = ConfigurationManager.GetSection(SectionName) as InstrumentationConfiguration ??
                        new InstrumentationConfiguration();
        }

        internal bool IsSubsystemNamespace(string ns)
        {
            bool matched = false;
            foreach (Regex regex in this.subsystemNamespaces)
            {
                if (regex.Match(ns).Success)
                {
                    matched = true;
                    break;
                }
            }
            return matched;
        }
    }

    public interface IInstrumentationConfiguration
    {
        /// <summary>
        /// Gets or sets the trace writer settings.
        /// </summary>
        WriterElement TraceWriter { get; set; }
        /// <summary>
        /// Gets or sets the log writer settings.
        /// </summary>
        WriterElement LogWriter { get; set; }
        /// <summary>
        /// Gets or sets the tracing configuration settings.
        /// </summary>
        TracingFlags Tracing { get; set; }
        /// <summary>
        /// Gets or sets the tracing configuration settings.
        /// </summary>
        LoggingFlags Logging { get; set; }
        /// <summary>
        /// Gets or sets the system name
        /// </summary>
        string System { get; set; }
        /// <summary>
        /// Gets or sets the subsystem name
        /// </summary>
        string Subsystem { get; set; }
        /// <summary>
        /// Gets or sets the subsystem name
        /// </summary>
        string SubsystemNamespaces { get; set; }
        /// <summary>
        /// Gets or sets the description
        /// </summary>
        string Description { get; set; }
    }
}